﻿using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using SoldiHive.Core.Persistence;
using SoldiHive.Model;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;

namespace SoldiHive.Core.Parsing
{
	public class Parser : IParser
	{
		private ICompanyRepository companyRepository;

		public Parser(ICompanyRepository companyRepository)
		{
			this.companyRepository = companyRepository;
		}

		public DateTime? ParseDate(string value)
		{
			DateTime date;
			
			var culture = CultureInfo.InvariantCulture;
			var styles = DateTimeStyles.None;
			
			if (DateTime.TryParseExact(value, "MMM dd, yyyy", culture, styles, out date))
			{
				return date;
			}
			else if (DateTime.TryParseExact(value, "MMM d, yyyy", culture, styles, out date))
			{
				return date;
			}
			else if (DateTime.TryParseExact(value, "M/d/yyyy", culture, styles, out date))
			{
				return date;
			}
			else if (DateTime.TryParseExact(value, "yyyy-MM-dd", culture, styles, out date))
			{
				return date;
			}

			return null;
		}

		public IList<DateTime> ParseDates(string value)
		{
			var datesTokens = JsonConvert.DeserializeObject(value) as JArray;
			if (datesTokens == null)
			{
				return null;
			}

			var datesStatements = new List<DateTime>();
			foreach (var dateToken in datesTokens)
			{
				if (dateToken == null)
				{
					continue;
				}

				var date = this.ParseDate(dateToken.ToString());
				if (date.HasValue)
				{
					datesStatements.Add(date.Value); 
				}
			}

			return datesStatements;
		}

		public OpinionAction ParseAction(string value)
		{
			var action = OpinionAction.NA;
			if (string.IsNullOrEmpty(value))
			{
				return action;
			}

			value = value.ToLower();
			Func<string, bool> contains = delegate(string text)
			{
				return value.Contains(text);
			};

			if (contains("upgrades"))
			{
				action = OpinionAction.Upgrades;
			}
			else if (contains("maintains"))
			{
				action = OpinionAction.Maintains;
			}
			else if (contains("new") && contains("coverage"))
			{
				action = OpinionAction.NewCoverage;
			}
			else if (contains("initiates") && contains("coverage"))
			{
				action = OpinionAction.InitiatesCoverage;
			}
			else if (contains("downgrade"))
			{
				action = OpinionAction.Downgrades;
			}

			return action;
		}

		public Opinion ParseOpinion(string value)
		{
			var estimate = Opinion.NA;
			if (string.IsNullOrEmpty(value))
			{
				return estimate;
			}

			value = value.ToLower();
			Func<string, bool> contains = delegate(string text)
			{
				return value.Contains(text);
			};

			if (contains("outperform") || contains("overweight") || (contains("strong") && contains("buy")))
			{
				estimate = Opinion.StrongBuy;
			}
			else if (contains("underperform") || contains("underweight") || (contains("strong") && contains("sell")))
			{
				estimate = Opinion.StrongSell;
			}
			else if (contains("buy"))
			{
				estimate = Opinion.Buy;
			}
			else if (contains("sell"))
			{
				estimate = Opinion.Sell;
			}
			else if (contains("hold") || contains("equalweight") || contains("marketperform") || (contains("market") && contains("perform")) || contains("neutral"))
			{
				estimate = Opinion.Hold;
			}
			else
			{
				var number = 0D;
				if (double.TryParse(value, out number))
				{
					const double maxNumberRate = 5;

					number = (maxNumberRate - number + 0.5) * Rating.MaxRate / maxNumberRate;
					estimate = this.ParseOpinion(number);
				}
			}

			return estimate;
		}

		public Opinion ParseOpinion(double? rate)
		{
			if (!rate.HasValue)
			{
				return Opinion.NA;
			}

			if (rate == Rating.MaxRate)
			{
				return Opinion.StrongBuy;
			}

			var opinions = Enum.GetValues(typeof(Opinion))
				.Cast<Opinion>()
				.Where(p => p != Opinion.NA)
				.OrderBy(ks => ks)
				.ToList();

			var percentile = opinions.Count * rate.Value / Rating.MaxRate;
			var opinionIndex = (int)Math.Floor(percentile);

			var opinion = opinions[opinionIndex];

			return opinion;
		}

		public double? ParseDouble(string value)
		{
			double doubleValue;
			if (double.TryParse(value, out doubleValue))
			{
				return doubleValue;
			}

			return null;
		}

		public int? ParseInteger(string value)
		{
			int integerValue;
			if (int.TryParse(value, out integerValue))
			{
				return integerValue;
			}

			return null;
		}

		public Time? ParseTime(string value)
		{
			Time time;
			if (Enum.TryParse<Time>(value, true, out time))
			{
				return time;
			}

			return Time.NA;
		}


		public AnalystOpinion ParseAnalystOpinion(string value)
		{
			var analystOpinionObject = new
			{
				Date = string.Empty,
				Firm = string.Empty,
				Action = string.Empty,
				Previous = string.Empty,
				Current = string.Empty,
				PriceTarget = new Nullable<double>(),
				OpinionsCount = new Nullable<int>()
			};

			analystOpinionObject = JsonConvert.DeserializeAnonymousType(value, analystOpinionObject);

			var analystOpinion = new AnalystOpinion()
			{
				Date = this.ParseDate(analystOpinionObject.Date),
				Firm = analystOpinionObject.Firm ?? string.Empty,
				Action = this.ParseAction(analystOpinionObject.Action),
				Previous = this.ParseOpinion(analystOpinionObject.Previous),
				Current = this.ParseOpinion(analystOpinionObject.Current),
				PriceTarget = analystOpinionObject.PriceTarget,
				OpinionsCount = analystOpinionObject.OpinionsCount
			};

			return analystOpinion;
		}

		public IList<AnalystOpinion> ParseAnalystOpinions(string value)
		{
			var analystOpinionTokens = JsonConvert.DeserializeObject(value) as JArray;
			if (analystOpinionTokens == null)
			{
				return null;
			}

			var analystOpinions = new List<AnalystOpinion>();
			foreach (var analystOpinionToken in analystOpinionTokens)
			{
				var analystOpinionObject = analystOpinionToken as JObject;
				if (analystOpinionObject == null)
				{
					return null;
				}

				var analystOpinion = this.ParseAnalystOpinion(analystOpinionObject.ToString());
				analystOpinions.Add(analystOpinion);
			}

			return analystOpinions;
		}

		public EarningsStatement ParseEarningsStatement(string value)
		{
			var earningsStatementObject = new 
			{
				Ticker = string.Empty,
				Date = string.Empty,
				Time = string.Empty,
			};

			earningsStatementObject = JsonConvert.DeserializeAnonymousType(value, earningsStatementObject);

			if (earningsStatementObject == null || 
				string.IsNullOrEmpty(earningsStatementObject.Ticker) || 
				string.IsNullOrEmpty(earningsStatementObject.Date))
			{
				return null;
			}

			var earningsStatement = new EarningsStatement()
			{
				Company = this.companyRepository.GetCompany(earningsStatementObject.Ticker),
				Date = this.ParseDate(earningsStatementObject.Date).Value,
				Time = this.ParseTime(earningsStatementObject.Time).Value
			};

			return earningsStatement;
		}

		public IList<EarningsStatement> ParseEarningsStatements(string value)
		{
			var earningsStatementTokens = JsonConvert.DeserializeObject(value) as JArray;
			if (earningsStatementTokens == null)
			{
				return null;
			}

			var earningsStatements = new List<EarningsStatement>();
			foreach (var earningsStatementToken in earningsStatementTokens)
			{
				var earningsStatementObject = earningsStatementToken as JObject;
				if (earningsStatementObject == null)
				{
					continue;
				}

				var earningsStatement = this.ParseEarningsStatement(earningsStatementObject.ToString());
				earningsStatements.Add(earningsStatement);
			}

			return earningsStatements;
		}
	}
}
